<!DOCTYPE html>
<html>
  <head>
    <!-- [[! Document Settings ]] -->
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

    <!-- [[! Page Meta ]] -->
    <title>[译] Android 架构：Part 3 —— 应用 Clean Architecture</title>
    <meta name="description" content="在编码 - " />

    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="shortcut icon" href="/assets/images/favicon.ico" >

    <!-- [[! Styles'n'Scripts ]] -->
    <link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
    <link rel="stylesheet" type="text/css"
      href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />

    <link rel="stylesheet" type="text/css" href="/assets/css/prism.css" />

    <!-- [[! Ghost outputs important style and meta data with this tag ]] -->
    <link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />

<meta property="og:site_name" content="在编码" />
<meta property="og:type" content="website" />
<meta property="og:title" content="在编码" />
<meta property="og:description" content="" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="在编码" />
<meta name="twitter:description" content="" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />

<script type="application/ld+json">
  {
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "Finding The Way Home",
  "url": "/",
  "image": "/assets/images/cover1.jpg",
  "description": ""
  }
</script>

<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="在编码" href="/feed.xml" />


  </head>
  <body class="home-template nav-closed">

    <div class="nav">
  <h3 class="nav-title">Home</h3>
  <a href="#" class="nav-close">
    <span class="hidden">Close</span>
  </a>
  <ul>
    <li class="nav-home " role="presentation"><a href="/">Home</a></li>
    <!-- <li class="nav-about " role="presentation"><a href="/about">About</a></li>
    <li class="nav-author " role="presentation"><a href="/author/casper">Author</a></li> -->
    <li class="nav-ios " role="presentation"><a href="/tag/iOS">iOS</a></li>
    <li class="nav-mvc " role="presentation"><a href="/tag/Android">Android</a></li>
    <li class="nav-server " role="presentation"><a href="/tag/Server">Server</a></li>
  </ul>
  <a class="subscribe-button icon-feed" href="/feed.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>


    <div class="site-wrapper">

      <!-- [[! Everything else gets inserted here ]] -->
      <!-- < default -->

<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->

<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->

<header class="main-header post-head no-cover">
  <nav class="main-nav overlay clearfix">
      <a class="back-button icon-arrow-left" href='/'>Home</a>
    <a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
  </nav>
</header>

<main class="content" role="main">

  <article class="post">

    <header class="post-header">
      <h1 class="post-title">[译] Android 架构：Part 3 —— 应用 Clean Architecture</h1>
      <section class="post-meta">
        <a href='/'>Listen</a>
        <time class="post-date" datetime="2017-07-27">27 Jul 2017</time>
        <!-- [[tags prefix=" on "]] -->
        
        on
        
        
        <a href='/tag/Android'>Android</a>,
        
        
        
        <a href='/tag/Clean Architecture'>Clean Architecture</a>
        
        
        
      </section>
    </header>

    <section class="post-content">

      <p>到目前为止，在这个系列中，我们已经讲解了一些 <a href="./android-architecture-part-1-every-new-beginning-is-hard">初学者易犯的错误</a>，以及过了一遍 <a href="./android-architecture-part-2-clean-architecture">Clean Architecture</a>。在这最后一部分，我们会介绍最后一个难题：标签，或者准确地说，组件。</p>

<blockquote>
<p>译者注：看完了这一部分，还有第四部分。在第四部分将会提供一个很酷的示范项目。</p>
</blockquote>

<p>首先，我会移除在 Android 项目中不使用的东西，添加一些在 Uncle
Bob 的图中找不到的但我们需要使用的东西，看起来像这样：</p>

<p><img src="../images/android/Graph-1-2.png" alt=""></p>

<p>我会从中心（抽象）讲到边缘（具体）。</p>

<h2>Entities</h2>

<p>实体（又称领域对象或业务对象）是 app 的核心。它们代表 app 的主要功能，你应该能够仅通过查看实体来说出 app 是做什么的。它们包含业务逻辑 —— 仅限于验证和类似的东西。它们不和真实的外部世界交互，也不会处理持久化。如果你有一个新闻 app，那么实体就是类别、文章和商业。</p>

<h2>Use Cases</h2>

<p>用例，也称为 interactor（交互器），也可以叫做 business service (业务服务对象)，是实体的扩展，是 business logic（业务逻辑）的扩展。也就是说，它们包含的业务逻辑不限于一个实体，而是可以处理很多实体。一个好用例的标准是，你可以使用一句简单的常用语言来描述它是做什么的，例如，“把钱从一个账户转移到另一个账户”。你甚至可以使用这样的命名系统来命名该类，例如 TransferMoneyUseCase。</p>

<h2>Repositories</h2>

<p>仓库用于持久化实体。就这么简单。它们定义为接口，并且用在要对实体执行增删查改操作的用例的输出端口。此外，它们可以公开一些与持久化相关的复杂操作，譬如过滤、聚合等。具体持久化策略，譬如数据库或网络，在外层中实现。例如，你可以把接口命名为 AccountRepository。</p>

<h2>Presenters</h2>

<p>如果你熟悉 MVP 模式，Presenter 做你想让它做的事情。它们处理用户交互，调用恰当的业务逻辑，并将数据发送给 UI 渲染。这里通常有各种类型的模型之间的映射转换。有些人会在这里使用控制器，这是可以的。我们使用的 Presenter 被正式称为监督控制器，我们通常根据屏幕方向为每个界面定义一个或两个 Presenter，并且 Presenter 的生命周期和相关的 View 的生命周期绑定。一个建议：尝试以技术无关的方式命名 Presenter 中的方法，假装你不知道 View 是用什么技术实现的。所以，如果在 View 中有方法名为 onSubmitOrderButtonClicked 和 onUserListItemSelected，那么处理这些事件的相应的 Presenter 中的方法可以被命名为 submitOrder 和 selectUser。</p>

<h2>Device</h2>

<p>这个组件在先前通知那个例子中已经被玩坏了。它包含了诸如传感器、闹钟、通知、播放器、各种 *Manager 等等真实 Android 功能的实现。它包含两部分组件。第一部分是定义在内层的接口，业务逻辑用它来作为和外部世界通信的输出端口。第二部分，也画在图中，是那些接口的实现。因此，比如，你可以定义名为 Gyroscope, Alarm, Notifications, 和 Player 的接口。请注意，这些名称是抽象的技术无关的。业务逻辑不关心通知如何显示，播放器如何播放声音，或螺旋仪的数据来自哪里。你可以创建一个将通知写入终端，将声音数据写到日志，或者从预先定义好的文件中收集螺旋仪数据的实现。这样的实现对于调试或创建一个用于你编码的确定性的环境是很有用的。当然，你必须创建诸如 AndroidAlarm，NativePlayer 等等的实现。在大多数情况下，这些实现仅仅是 Android Manager 类的包装。</p>

<h2>DB &amp; API</h2>

<p>这里没有哲学。将仓库的实现放在此组件中。所有的底层持久化的东西应该放在这里：DAO，ORM，Retrofit（或别的），JSON 解析等等。你还可以在这里实现缓存策略或者简单地在内存中（in-memory）持久化，直到你完成了 app 的其余部分。我们团队最近进行了一个有趣的讨论。问题是这样：仓库是否应该公开诸如 fetchUsersOffline（fetchUsersFromCache）和 fetchUsersOnline（fetchUsersFromInternet）之类的方法？换句话说，业务逻辑是否应该知道数据来自哪里。读完这篇文章的所有内容后，答案很简单：不。但这里有个陷阱。如果关于数据源的决策是业务逻辑的一部分 —— 譬如，用户可以选择或者 app 有一个明确的离线模式 —— 然后你<strong>可以</strong>添加这样的区分。但我不会为每个请求定义两种方法。我可能会在仓库中公开 enterOfflineMode 和 exitOfflineMode 这样的方法。或者如果它适用于所有仓库，我们可以使用 enter 和 exit 方法定义一个 OfflineMode 接口，并在业务端使用它，让仓库去查询它的模式并且在内部决策。</p>

<h2>UI</h2>

<p>这里的哲学更少。将和 Android UI 相关的东西放在这里。Activity、Fragment、View、Adapter 等等。完了。</p>

<h2>模块（Modules）</h2>

<p>下图显示了我们如何将所有这些组件分解成 Android Studio 模块。 你可能会发现另一种更合适的分法。</p>

<p><img src="../images/android/Graph-2-2.png" alt=""></p>

<p>我们将实体、用例、仓库和设备接口分到领域模块。如果你想要一个额外的挑战（奖励是永恒的荣耀和完全整洁的设计），你可以使该模块成为一个纯 java 模块。这将阻止你走捷径将一些 Android 相关的东西放在这里。</p>

<p>设备模块包含所有和 Android 相关的东西（除了数据持久化和 UI）。数据模块应该持有和数据持久化相关的东西，正如我们说过的那样。你不能把这两者弄成 java 模块，因为它们需要访问各种 Andriod 相关的东西。你可以把它们弄成 Android library。</p>

<p>最后，我们将和 UI （包括 Presenter）相关的所有东西分到 UI 模块。你可以明确地将其命名为 UI，但是由于所有 Android 的东西都在这里，我们保留它 “app” 的名字，正如 Android Studio 在创建项目时所命名的那样。</p>

<h2>好点了吗？</h2>

<p>为了回答这个问题，我丢开 Uncle Bob 的图，将先前描述的组件摊开到图中，就像之前那些我们曾经评估过的架构类型那样。这样做之后，我们得到：</p>

<p><img src="../images/android/Graph-3-.png" alt=""></p>

<p>现在让我们来使用与之前的架构相同的评价标准。</p>

<p><img src="../images/android/Graph-4-1.png" alt=""></p>

<p>它完全分离到模块级别、包级别、类级别，所以应该满足单一职责原则。</p>

<p><img src="../images/android/Graph-5-1.png" alt=""></p>

<p>我们已经将 Android 和真实世界的东西尽可能地推到边缘，业务逻辑再也没有直接接触 Android。</p>

<p><img src="../images/android/Graph-6-1.png" alt=""></p>

<p>我们很好地分离类以方便测试。接触 Android 世界的类可以使用 Android 测试例进行测试，没有接触的类可以使用 JUnit 进行测试。可能有人恶意称它为类爆炸，我称之为可测试。:)</p>

<p><img src="../images/android/Graph-7-1.png" alt=""></p>

<h2>这可能很复杂——但值得</h2>

<p>我希望我精心挑选的标准，不仅能迎合 Clean Architecture，也能说服你一试。看起来很复杂，而且有很多细节，但这是值得的。一旦你把所有都串起来，测试就会变的更容易，BUG 更容易定位，新功能更容易添加，代码更易读和维护，一切都可以完美运行，宇宙都被满足了。</p>

<p>所以，就是这样。如果你还没有这样做，请看本系列先前的文章：<a href="./android-architecture-part-1-every-new-beginning-is-hard.html">初学者易犯的错误</a> 和 <a href="./android-architecture-part-2-clean-architecture.html">介绍 Clean Architecture</a>。如果你有任何意见或问题，请留言。我们总是有兴趣听到你的想法。</p>

<p>阅读 Andriod 架构系列 <a href="./android-architecture-part-4-applying-clean-architecture-on-android-hands-on.html">第四部分</a></p>

<p><a href="http://five.agency/android-architecture-part-3-applying-clean-architecture-android/">原文</a></p>


    </section>

    <footer class="post-footer">

      <!-- Everything inside the #author tags pulls data from the author -->
      <!-- #author-->

      
      <figure class="author-image">
        <a class="img" href='/' style="background-image: url(/assets/images/me.jpg)"><span class="hidden">'s Picture</span></a>
      </figure>
      

      <section class="author">
        <h4><a href='/'>Listen</a></h4>

        
        <p> 移动开发工程师，熟悉 Android、iOS、ReactNative</p>
        
        <div class="author-meta">
          <span class="author-location icon-location"> 深圳</span>
          <span class="author-link icon-link"><a href="https://github.com/listenzz"> Github</a></span>
        </div>
      </section>

      <!-- /author  -->

      <section class="share">
        <h4>Share this post</h4>
        <a class="icon-twitter" href="http://twitter.com/share?text=[译] Android 架构：Part 3 —— 应用 Clean Architecture&amp;url=https://github.com/listenzzandroid-architecture-part-3-applying-clean-architecture-android.html"
          onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
          <span class="hidden">Twitter</span>
        </a>
        <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://github.com/listenzzandroid-architecture-part-3-applying-clean-architecture-android.html"
          onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
          <span class="hidden">Facebook</span>
        </a>
        <a class="icon-google-plus" href="https://plus.google.com/share?url=https://github.com/listenzzandroid-architecture-part-3-applying-clean-architecture-android.html"
          onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
          <span class="hidden">Google+</span>
        </a>
      </section>

      <!-- Add Disqus Comments -->
      <!-- <section class="disqus">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
        var disqus_shortname  = 'listenzz';
        var disqus_identifier = '[译] Android 架构：Part 3 —— 应用 Clean Architecture';
        var disqus_title      = "[译] Android 架构：Part 3 —— 应用 Clean Architecture";
        
        (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
         })();
        </script>
    <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
 -->

    </footer>

  </article>

</main>

<aside class="read-next">

  <!-- [[! next_post ]] -->
  
  <a class="read-next-story no-cover" href="/android-architecture-part-4-applying-clean-architecture-on-android-hands-on.html">
    <section class="post">
      <h2>[译] Android 架构：Part 4 —— 实践 Clean Architecture（含源码）</h2>
      <p>![](/images/android/main_image.png) 在 [Android 架构系列的最后部分](./android-architecture-part-3-applying-clean-architecture-android)，我们将 Clean Architecture 调整到 Android 平台。我们将 Android 和真实世界从业务逻辑中分离，令利益相关者满意，使一切都容易测试。 理论很棒，但是当我们创建一个新 Android 项目时，该从哪开始呢？让我们用整洁代码弄脏我们的手，把空白画布变成一个架构。 ##...</p>
    </section>
  </a>
  
  <!-- [[! /next_post ]] -->
  <!-- [[! prev_post ]] -->
  
  <a class="read-next-story prev no-cover" href="/android-architecture-part-2-clean-architecture.html">
    <section class="post">
      <h2>[译] Android 架构：Part 2 —— 介绍 Clean Architecture</h2>
      <p>在本系列的第一部分，我们介绍了我们在寻找可行架构的道路上所犯过的错误。在这部分，我们将介绍传说中的 Clean Architecture。 当你在谷歌搜索 &quot;clean architecture&quot; 时，你看到的第一张图片是： 它也被称为洋葱架构，因为图看起来象个洋葱（你会意识到你需要写样板代码写到哭）；或者是端口和适配器，因为你可以看到右图的一些端口。六角架构是另一个相似的架构。 Clean Architecture 是前面提到的 Uncle Bob 的心血结晶，他是 《代码整洁之道》的作者。这种方法的要点是，业务逻辑（也称为...</p>
    </section>
  </a>
  
  <!-- [[! /prev_post ]] -->
</aside>

<!-- /post -->


      <footer class="site-footer clearfix">
        <section class="copyright"><a href="/">在编码</a> &copy; 2020</section>
        <section class="poweredby">Proudly published with <a href="https://jekyllrb.com/">Jekyll</a> using <a href="https://github.com/biomadeira/jasper">Jasper</a></section>
      </footer>
    </div>
    <!-- [[! Ghost outputs important scripts and data with this tag ]] -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
    <!-- [[! The main JavaScript file for Casper ]] -->
    <script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
    <script type="text/javascript" src="/assets/js/index.js"></script>
    <script type="text/javascript" src="/assets/js/prism.js"></script>

    <!-- Add Google Analytics  -->
    <!-- Google Analytics Tracking code -->
<script>
 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
   (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
   m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-96120107-1', 'auto');
 ga('send', 'pageview');

</script>

  </body>
</html>
